package com.qkun.library.utils;

import android.content.res.AssetFileDescriptor;
import android.media.AudioAttributes;
import android.media.AudioManager;
import android.media.RingtoneManager;
import android.media.SoundPool;
import android.net.Uri;
import android.os.Build;
import android.text.TextUtils;

import androidx.annotation.FloatRange;
import androidx.annotation.IntDef;
import androidx.annotation.IntRange;
import androidx.annotation.NonNull;
import androidx.annotation.RawRes;

import com.qkun.library.base.BaseApplication;

import java.io.FileDescriptor;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;

/**
 * @author qinkun
 * @date 6/20 0020 17:33
 * @description 1、SoundPool适用于短暂且反应速度要求比较高的情况（如：游戏音效、按键提示音、消息等），
 * 文件大小一般要控制在1M以下，因为其最大只能申请到1MB的内存空间
 * 2、SoundPool可以同时播放多个声音（包含与MediaPlayer同时播放）
 * 3、MediaPlayer只能同时播放一个声音，加载文件要一定的时间，适用于文件比较大，响应时间要求不高的场景
 * 4、SoundPool与MediaPlayer的最终编解码实现均相同
 * <p>
 * 注意：
 * 使用完记得回收资源 release()
 * </p>
 */
public final class SoundPoolCompat {

    private final ArraysEx.Comparator SOUND_KEY_COMPARATOR = new ArraysEx.Comparator<Sound, String>() {
        @Override
        public int compare(Sound o, String value) {
            return o.key.compareTo(value);
        }
    };
    private SoundPool mSoundPool;
    private Sound[] mSounds;
    private OnLoadCompleteListener mOnLoadCompleteListener;

    /**
     * @param maxStream  SoundPool对象的最大并发流数(即可以同时播放的最大同时流数)
     * @param streamType AudioManager中描述的音频流类型
     */
    private SoundPoolCompat(@IntRange(from = 1, to = Integer.MAX_VALUE) int maxStream, @STREAM_TYPE int streamType) {
        if (maxStream < 1) {
            throw new IllegalArgumentException("This \"maxStream\" must be greater than 0");
        }
        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
            SoundPool.Builder builder = new SoundPool.Builder();
            builder.setMaxStreams(maxStream);
            //AudioAttributes是一个封装音频各种属性的类
            AudioAttributes.Builder attrBuilder = new AudioAttributes.Builder();
            //设置音频流类型
            attrBuilder.setLegacyStreamType(streamType);
            //加载AudioAttributes
            builder.setAudioAttributes(attrBuilder.build());
            mSoundPool = builder.build();
        } else {
            //srcQuality：采采样率转换器质量，目前无效。默认使用0
            mSoundPool = new SoundPool(maxStream, streamType, 0);
        }
        //等待对应的音频异步加载完成才可以成功播放
        mSoundPool.setOnLoadCompleteListener(new SoundPool.OnLoadCompleteListener() {
            @Override
            public void onLoadComplete(SoundPool soundPool, int sampleId, int status) {
                if (Utils.nonNull(mSounds)) {
                    int index = checkId(sampleId);
                    if (ArraysEx.isIncludeIndex(mSounds, index)) {
                        //加载音频操作的状态（0表示加载成功）
                        boolean result = (status == 0);
                        Sound sound = mSounds[index];
                        sound.available = result;
                        if (Utils.nonNull(mOnLoadCompleteListener)) {
                            mOnLoadCompleteListener.onLoadComplete(SoundPoolCompat.this, sound.key, result);
                        }
                    }
                }
            }
        });
    }

    private void setOnLoadCompleteListener(OnLoadCompleteListener onLoadCompleteListener) {
        this.mOnLoadCompleteListener = onLoadCompleteListener;
    }

    private void onLoad(Sound[] sounds) {
        this.mSounds = sounds;
        if (Utils.nonNull(sounds)) {
            for (Sound sound : sounds) {
                sound.onLoad(this);
            }
        }
    }

    /**
     * 通过一个资源ID加载音频资源，多次加载同一个资源返回的soundId是不同的，
     * 并且载入的时候使用的是独立的线程，不会阻塞UI主线程
     *
     * @param resId
     * @return
     */
    private int load(@RawRes int resId) {
        if (Utils.nonNull(mSoundPool)) {
            //priority: 声音的优先级，目前没有效果，建议设为1
            return mSoundPool.load(BaseApplication.getAppContext(), resId, 1);
        }
        return 0;
    }

    /**
     * 通过指定的路径加载音频资源
     *
     * @param ringtonePath
     * @return
     */
    private int load(@NonNull String ringtonePath) {
        if (Utils.nonNull(mSoundPool)) {
            //priority: 声音的优先级，目前没有效果，建议设为1
            return mSoundPool.load(ringtonePath, 1);
        }
        return 0;
    }

    /**
     * 通过AssetFileDescriptor对象来加载音频资源
     *
     * @param afd
     * @return
     */
    private int load(@NonNull AssetFileDescriptor afd) {
        if (Utils.nonNull(mSoundPool)) {
            //priority: 声音的优先级，目前没有效果，建议设为1
            return mSoundPool.load(afd, 1);
        }
        return 0;
    }

    /**
     * 通过FileDescriptor对象来加载音频资源
     *
     * @param fd
     * @param offset
     * @param length
     * @return
     */
    private int load(@NonNull FileDescriptor fd, long offset, long length) {
        if (Utils.nonNull(mSoundPool)) {
            //priority: 声音的优先级，目前没有效果，建议设为1
            return mSoundPool.load(fd, offset, length, 1);
        }
        return 0;
    }

    /**
     * 通过uri加载音频资源
     *
     * @param ringtoneUri
     * @return
     */
    private int load(@NonNull Uri ringtoneUri) {
        String ringtonePath = Utils.uriToPath(ringtoneUri);
        if (!TextUtils.isEmpty(ringtonePath)) {
            return load(ringtonePath);
        }
        return 0;
    }

    /**
     * 根据ringtoneType加载系统默认的铃声
     *
     * @param ringtoneType
     * @return
     */
    private int loadDefault(@RINGTONE_TYPE int ringtoneType) {
        Uri uri = getSystemDefaultRingtoneUri(ringtoneType);
        if (Utils.nonNull(uri)) {
            return load(uri);
        }
        return 0;
    }

    /**
     * 播放对应soundId的音频，当soundId为0的时候表示加载失败
     *
     * @param key
     * @param leftVolume  左侧音量（范围：0.0~1.0）
     * @param rightVolume 右侧音量（范围：0.0~1.0）
     * @param priority    流的优先级，值越大优先级高，影响当同时播放数量超出了
     *                    最大支持数时SoundPool对该流的处理（0：最低优先级）
     * @param loop        音频重复播放次数（-1：无限循环，0：一次，其他值：loop+1次）
     * @param rate        播放的速率（范围：0.5~2.0）（0.5：一半速率，1.0：正常速率，2.0：两倍速率）
     * @return 返回这次播放的streamId
     */
    public int play(@NonNull String key,
                    @FloatRange(from = 0.0, to = 1.0) float leftVolume,
                    @FloatRange(from = 0.0, to = 1.0) float rightVolume,
                    int priority,
                    @IntRange(from = -1, to = Integer.MAX_VALUE) int loop,
                    @FloatRange(from = 0.5, to = 2.0) float rate) {
        if (Utils.nonNull(mSoundPool) && Utils.nonNull(mSounds)) {
            int index = checkKey(key);
            if (ArraysEx.isIncludeIndex(mSounds, index)) {
                Sound sound = mSounds[index];
                if (sound.available) {
                    // 当play返回的streamId等于0的时候表示播放失败
                    // 每play一次，返回的streamId就会递增一次（该streamID对应的是多次play中的某一次，可用于控制该次播放）
                    return mSoundPool.play(sound.id, leftVolume, rightVolume, priority, loop, rate);
                }
            }
        }
        return 0;
    }

    public int play(@NonNull String key, @IntRange(from = -1, to = Integer.MAX_VALUE) int loop) {
        return play(key, 1, 1, 1, loop, 1);
    }

    public int play(@NonNull String key, boolean isLoop) {
        return play(key, isLoop ? -1 : 0);
    }

    public int play(@NonNull String key) {
        return play(key, false);
    }

    /**
     * 暂停streamId对应的那次播放（当且仅当该次播放还在播放时有效）
     *
     * @param streamId
     */
    public void pause(int streamId) {
        if (Utils.nonNull(mSoundPool) && (streamId > 0)) {
            mSoundPool.pause(streamId);
        }
    }

    /**
     * 继续streamId对应的那次已暂停的播放（当且仅当该次播放还存在且处于暂停状态时有效）
     *
     * @param streamId
     */
    public void resume(int streamId) {
        if (Utils.nonNull(mSoundPool) && (streamId > 0)) {
            mSoundPool.resume(streamId);
        }
    }

    /**
     * 停止streamId对应的那次播放,还将释放与该次播放相关联的资源
     * （当且仅当该次播放未停止时有效，正常情况下播放结束就是处于停止状态了）
     *
     * @param streamId
     */
    public void stop(int streamId) {
        if (Utils.nonNull(mSoundPool) && (streamId > 0)) {
            mSoundPool.stop(streamId);
        }
    }

    /**
     * 将遍历所有活动流并暂停当前正在播放的任何流
     * 还将设置一个标志，以便可以通过调用{@link SoundPool#autoResume()}来恢复正在播放的所有流
     * <p>
     * 注意：
     * 使用{@link SoundPool#resume(int)}是可以恢复对应streamId的那个流的，哪怕是由 autoResume() 暂停的
     * </p>
     */
    public void autoPause() {
        if (Utils.nonNull(mSoundPool)) {
            mSoundPool.autoPause();
        }
    }

    /**
     * 自动恢复先前调用{@link SoundPool#autoPause()}时暂停的所有流
     * <p>
     * 注意：
     * 是先前调用{@link SoundPool#autoPause()}时暂停的所有流，不单单是前面的一次，
     * 所以如果前面多次调用{@link SoundPool#autoPause()}，
     * 现在再调用 autoResume() 是会一次性恢复被{@link SoundPool#autoPause()}暂停的所有的流的，
     * 而被{@link SoundPool#pause(int)}暂停的流则不会被恢复
     * </p>
     */
    public void autoResume() {
        if (Utils.nonNull(mSoundPool)) {
            mSoundPool.autoResume();
        }
    }

    /**
     * 释放资源
     */
    public void release() {
        if (Utils.nonNull(mSoundPool)) {
            mSoundPool.release();
            mSoundPool = null;
            mSounds = null;
        }
    }

    /**
     * 根据ringtoneType获取系统默认铃声的Uri
     *
     * @param ringtoneType
     * @return
     */
    private Uri getSystemDefaultRingtoneUri(@RINGTONE_TYPE int ringtoneType) {
        try {
            return RingtoneManager.getActualDefaultRingtoneUri(BaseApplication.getAppContext(), ringtoneType);
        } catch (Exception e) {
            MyLog.e(e);
        }
        return null;
    }

    private int checkKey(String key) {
        if (Utils.nonNull(mSounds)) {
            return ArraysEx.binarySearch(mSounds, key, SOUND_KEY_COMPARATOR);
        }
        return -1;
    }

    private int checkId(int id) {
        if (Utils.nonNull(mSounds)) {
            for (int i = 0; i < mSounds.length; i++) {
                if (mSounds[i].id == id) {
                    return i;
                }
            }
        }
        return -1;
    }

    /**
     * 判断key对应的那个音频是否有效
     *
     * @param key
     * @return
     */
    public boolean isAvailableSound(String key) {
        //该sound依赖于soundPool，如果soundPool为null，则该sound也是无效的
        if (Utils.nonNull(mSoundPool) && Utils.nonNull(mSounds)) {
            int index = checkKey(key);
            if (ArraysEx.isIncludeIndex(mSounds, index)) {
                return mSounds[index].available;
            }
        }
        return false;
    }

    @IntDef({AudioManager.STREAM_MUSIC, AudioManager.STREAM_ALARM, AudioManager.STREAM_RING})
    @Retention(RetentionPolicy.SOURCE)
    public @interface STREAM_TYPE {
    }

    @IntDef({RingtoneManager.TYPE_ALARM, RingtoneManager.TYPE_NOTIFICATION, RingtoneManager.TYPE_RINGTONE})
    @Retention(RetentionPolicy.SOURCE)
    public @interface RINGTONE_TYPE {
    }

    public interface OnLoadCompleteListener {
        void onLoadComplete(SoundPoolCompat soundPool, String key, boolean result);
    }

    private static abstract class Sound implements Comparable<Sound> {

        //对应加载的资源，将会通过它来控制播放对应的音频
        protected String key;
        protected int id;
        protected boolean available;

        public Sound(@NonNull String key) {
            this.key = Utils.requireNonNull(key);
        }

        abstract int load(@NonNull SoundPoolCompat mSoundPool);

        public final void onLoad(@NonNull SoundPoolCompat soundPool) {
            id = load(Utils.requireNonNull(soundPool));
        }

        @Override
        public int compareTo(Sound o) {
            return key.compareTo(o.key);
        }
    }

    private static class RawSound extends Sound {

        private @RawRes
        int resId;

        public RawSound(@NonNull String key, int resId) {
            super(key);
            this.resId = resId;
        }

        @Override
        int load(@NonNull SoundPoolCompat soundPool) {
            return soundPool.load(resId);
        }
    }

    private static class PathSound extends Sound {

        private String ringtonePath;

        public PathSound(@NonNull String key, @NonNull String ringtonePath) {
            super(key);
            this.ringtonePath = Utils.requireNonNull(ringtonePath);
        }

        @Override
        int load(@NonNull SoundPoolCompat soundPool) {
            return soundPool.load(ringtonePath);
        }
    }

    private static class UriSound extends Sound {

        private Uri ringtoneUri;

        public UriSound(@NonNull String key, @NonNull Uri ringtoneUri) {
            super(key);
            this.ringtoneUri = Utils.requireNonNull(ringtoneUri);
        }

        @Override
        int load(@NonNull SoundPoolCompat soundPool) {
            return soundPool.load(ringtoneUri);
        }
    }

    private static class FileSound extends Sound {

        private FileDescriptor fd;
        private long offset;
        private long length;

        public FileSound(@NonNull String key, @NonNull FileDescriptor fd, long offset, long length) {
            super(key);
            this.fd = Utils.requireNonNull(fd);
            this.offset = offset;
            this.length = length;
        }

        @Override
        int load(@NonNull SoundPoolCompat soundPool) {
            return soundPool.load(fd, offset, length);
        }
    }

    private static class AssetFileSound extends Sound {

        private AssetFileDescriptor afd;

        public AssetFileSound(@NonNull String key, @NonNull AssetFileDescriptor afd) {
            super(key);
            this.afd = Utils.requireNonNull(afd);
        }

        @Override
        int load(@NonNull SoundPoolCompat soundPool) {
            return soundPool.load(afd);
        }
    }

    private static class DefaultSound extends Sound {

        private @RINGTONE_TYPE
        int ringtoneType;

        public DefaultSound(@NonNull String key, @RINGTONE_TYPE int ringtoneType) {
            super(key);
            this.ringtoneType = ringtoneType;
        }

        @Override
        int load(@NonNull SoundPoolCompat soundPool) {
            return soundPool.loadDefault(ringtoneType);
        }
    }

    public static class Builder {

        private @STREAM_TYPE
        int streamType = AudioManager.STREAM_MUSIC;
        private OnLoadCompleteListener onLoadCompleteListener;
        private Sound[] mSounds;
        private int maxStream;

        public Builder(int maxStream) {
            this.maxStream = Math.max(maxStream, 1);
        }

        public Builder setStreamType(@STREAM_TYPE int streamType) {
            this.streamType = streamType;
            return this;
        }

        public Builder load(@NonNull String key, @RawRes int resId) {
            mSounds = ArraysEx.merge(mSounds, new RawSound(key, resId));
            return this;
        }

        public Builder load(@NonNull String key, @NonNull String ringtonePath) {
            mSounds = ArraysEx.merge(mSounds, new PathSound(key, ringtonePath));
            return this;
        }

        public Builder load(@NonNull String key, @NonNull Uri ringtoneUri) {
            mSounds = ArraysEx.merge(mSounds, new UriSound(key, ringtoneUri));
            return this;
        }

        public Builder load(@NonNull String key, @NonNull FileDescriptor fd, long offset, long length) {
            mSounds = ArraysEx.merge(mSounds, new FileSound(key, fd, offset, length));
            return this;
        }

        public Builder load(@NonNull String key, @NonNull AssetFileDescriptor afd) {
            mSounds = ArraysEx.merge(mSounds, new AssetFileSound(key, afd));
            return this;
        }

        public Builder loadDefault(@NonNull String key, @RINGTONE_TYPE int ringtoneType) {
            mSounds = ArraysEx.merge(mSounds, new DefaultSound(key, ringtoneType));
            return this;
        }

        public Builder setOnLoadCompleteListener(OnLoadCompleteListener onLoadCompleteListener) {
            this.onLoadCompleteListener = onLoadCompleteListener;
            return this;
        }

        public SoundPoolCompat build() {
            SoundPoolCompat soundPoolCompat = new SoundPoolCompat(maxStream, streamType);
            soundPoolCompat.setOnLoadCompleteListener(onLoadCompleteListener);
            soundPoolCompat.onLoad(mSounds);
            return soundPoolCompat;
        }
    }
}
